package com.thz.houserental.infrastructure.web.service.impl;

import cn.hutool.core.util.StrUtil;
import cn.xuyanwu.spring.file.storage.FileStorageService;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.thz.houserental.domain.requirement.Requirement;
import com.thz.houserental.domain.requirement.RequirementService;
import com.thz.houserental.domain.reservation.Reservation;
import com.thz.houserental.domain.reservation.ReservationService;
import com.thz.houserental.domain.user.UserVo;
import com.thz.houserental.infrastructure.auth.AuthUtil;
import com.thz.houserental.infrastructure.auth.JwtUtil;
import com.thz.houserental.infrastructure.auth.UserDetail;
import com.thz.houserental.infrastructure.exception.ForbiddenException;
import com.thz.houserental.infrastructure.exception.NotFoundException;
import com.thz.houserental.infrastructure.table.User;
import com.thz.houserental.infrastructure.web.dto.EmailLoginDto;
import com.thz.houserental.infrastructure.web.dto.RegisterDto;
import com.thz.houserental.infrastructure.web.dto.WechatLoginDto;
import com.thz.houserental.infrastructure.web.mapper.UserMapper;
import com.thz.houserental.infrastructure.web.service.UserService;
import com.thz.houserental.infrastructure.web.vo.CurrentUser;
import org.springframework.context.annotation.Lazy;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.util.List;
import java.util.Set;
import java.util.stream.Collectors;

@Service
@Transactional
public class UserServiceImpl
        extends ServiceImpl<UserMapper, User>
        implements UserService, UserDetailsService {

    private final UserMapper userMapper;
    private final PasswordEncoder passwordEncoder;
    private final ReservationService reservationService;
    private final RequirementService requirementService;
    private final FileStorageService fileStorageService;

    public UserServiceImpl(UserMapper userMapper,
                           @Lazy PasswordEncoder passwordEncoder,
                           @Lazy ReservationService reservationService,
                           @Lazy RequirementService requirementService, FileStorageService fileStorageService) {
        this.userMapper = userMapper;
        this.passwordEncoder = passwordEncoder;
        this.reservationService = reservationService;
        this.requirementService = requirementService;
        this.fileStorageService = fileStorageService;
    }

    @Override
    public String registerNewUserAccount(RegisterDto userDto) {
        if (emailExists(userDto.getEmail())) {
            throw new ForbiddenException("There is an account with that email address: " + userDto.getEmail());
        }

        if (StrUtil.isNotEmpty(userDto.getWechatId()) && wechatExists(userDto.getWechatId())) {
            throw new ForbiddenException("There is an account with that wechatId: " + userDto.getWechatId());
        }

        User user = User.builder()
                .nickName(userDto.getNickName())
                .email(userDto.getEmail())
                .wechatId(userDto.getWechatId())
                .password(passwordEncoder.encode(userDto.getPassword()))
                .build();
        this.save(user);

        return JwtUtil.generate(user.getNickName());
    }

    @Override
    public String loginByEmail(EmailLoginDto loginDto) {
        User user = this.lambdaQuery()
                .eq(User::getEmail, loginDto.getEmail())
                .one();

        if (user == null || !passwordEncoder.matches(loginDto.getPassword(), user.getPassword())) {
            throw new ForbiddenException("账号密码错误");
        }

        return JwtUtil.generate(user.getId());
    }

    private boolean wechatExists(String wechatId) {
        return this.lambdaQuery()
                .eq(User::getWechatId, wechatId)
                .count() != 0;
    }

    private boolean emailExists(String email) {
        return this.lambdaQuery()
                .eq(User::getEmail, email)
                .count() != 0;
    }

    /**
     * 实际使用userId
     */
    @Override
    public UserDetails loadUserByUsername(String userId) {
        User user = this.lambdaQuery()
                .eq(User::getId, userId)
                .one();

        if (user == null) {
            throw new NotFoundException(10100);
        }

        // 将用户所有的角色查出来当作用户权限
        List<String> roles = this.getUserRolesByEmail(user.getEmail());
        Set<SimpleGrantedAuthority> grantedAuthorities = roles.stream()
                .map(s -> "ROLE_" + s)
                .map(SimpleGrantedAuthority::new)
                .collect(Collectors.toSet());

        return new UserDetail(user, grantedAuthorities);
    }

    @Override
    public List<String> getUserRolesByEmail(String email) {
        User user = this.lambdaQuery().eq(User::getEmail, email).one();
        return this.userMapper.getUserRoles(user.getId());
    }

    @Override
    public String loginByWechat(WechatLoginDto loginDto) {
        User user = this.lambdaQuery()
                .eq(User::getWechatId, loginDto.getWechatId())
                .one();

        if (user == null || !passwordEncoder.matches(loginDto.getPassword(), user.getPassword())) {
            throw new ForbiddenException("账号密码错误");
        }

        return JwtUtil.generate(user.getId());
    }

    @Override
    public CurrentUser getCurrentUser() {
        String currentUserId = AuthUtil.getCurrentUserId();
        User user = this.getById(currentUserId);
        List<String> roles = this.getUserRolesByEmail(user.getEmail());


        return CurrentUser.builder()
                .roles(roles)
                .userId(user.getId())
                .email(user.getEmail())
                .phone(user.getPhone())
                .nickName(user.getNickName())
                .wechatId(user.getWechatId())
                .build();
    }

    @Override
    public IPage<UserVo> selectPageVo(Page<UserVo> userVoPage, QueryWrapper<User> queryWrapper) {
        return this.userMapper.selectPageVo(userVoPage, queryWrapper);
    }


    @Override
    public UserVo getUserVo(String userId) {
        User user = this.getById(userId);
        Integer requirementCount = this.requirementService.lambdaQuery().eq(Requirement::getUserId, userId).count();
        Integer collectCount = this.reservationService.lambdaQuery()
                .eq(Reservation::getUserId, userId)
                .eq(Reservation::getIsCollect, 1)
                .count();

        Integer likeCount = this.reservationService.lambdaQuery()
                .eq(Reservation::getUserId, userId)
                .isNull(Reservation::getIsCollect)
                .or((wrapper) -> {
                    wrapper.eq(Reservation::getIsCollect, 0);
                })
                .count();


        return UserVo.builder()
                .userId(user.getId())
                .email(user.getEmail())
                .nickName(user.getNickName())
                .phone(user.getPhone())
                .wechatId(user.getWechatId())
                .collectNum(collectCount)
                .likeNum(likeCount)
                .requireNum(requirementCount)
                .build();
    }

    @Override
    public void updateUserDetail(User user) {
        String currentUserId = AuthUtil.getCurrentUserId();
        user.setId(currentUserId);
        User old = this.getById(currentUserId);

        //判断头像文件是否需要跟新
        //有且只有一种情况不需要删除旧文件，旧头像链接与新头像相同
        if (!(StrUtil.isNotEmpty(user.getAvatar()) 
                && StrUtil.isNotEmpty(old.getAvatar()) 
                && user.getAvatar().equals(old.getAvatar()))) {
           this.fileStorageService.delete(old.getAvatar());
        }
        this.updateById(user);
    }
}